﻿/**************************************************************
 *
 * 唯一标识：f559f471-62b2-4876-a85a-7b0a9e632708
 * 命名空间：Sgr.BackGroundTasks
 * 创建时间：2023/8/31 17:06:14
 * 机器名称：DESKTOP-S0D075D
 * 创建者：antho
 * 电子邮箱：fengqinhua2016@163.com
 * 描述：
 *
 **************************************************************/

using Quartz;
using Sgr.Utilities;
using System.Globalization;

namespace Sgr.BackGroundTasks
{
    /// <summary>
    /// 基于 Quartz.NET 实现的后台任务管理器
    /// </summary>
    public class QuartzBackGroundTaskManager : IBackGroundTaskManager
    {
        #region 常量定义

        /// <summary>
        /// 作业数据键名前缀
        /// </summary>
        public const string Prefix = "sgr-";

        /// <summary>
        /// 任务类型键名
        /// </summary>
        public const string KEY_TASK_CLASS_TYPE = Prefix + "TaskClassType";

        /// <summary>
        /// 任务数据对象键名
        /// </summary>
        public const string KEY_DATA_OBJECT = Prefix + "TDataObject";

        /// <summary>
        /// 最大重试次数键名
        /// </summary>
        public const string KEY_MAX_RETRY_COUNT = Prefix + "MaxRetryCountOnError";

        /// <summary>
        /// 重试间隔键名
        /// </summary>
        public const string KEY_RETRY_INTERVAL = Prefix + "RetryIntervalSecond";

        #endregion 常量定义

        private readonly ISchedulerFactory _schedulerFactory;

        /// <summary>
        /// 初始化 QuartzBackGroundTaskManager 实例
        /// </summary>
        /// <param name="factory">Quartz 调度器工厂</param>
        public QuartzBackGroundTaskManager(ISchedulerFactory factory)
        {
            _schedulerFactory = factory ?? throw new ArgumentNullException(nameof(factory));
        }

        /// <summary>
        /// 使用 BackGroundTaskBuilder 添加后台任务
        /// </summary>
        /// <param name="backGroundTaskBuilder">任务构建器</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>任务标识符</returns>
        /// <exception cref="ArgumentNullException">任务构建器为空时抛出</exception>
        /// <exception cref="BackGroundTaskException">任务配置无效时抛出</exception>
        public async Task<string> EnqueueAsync(BackGroundTaskBuilder backGroundTaskBuilder, CancellationToken cancellationToken = default)
        {
            Check.NotNull(backGroundTaskBuilder, nameof(backGroundTaskBuilder));

            // 验证任务配置
            if (string.IsNullOrEmpty(backGroundTaskBuilder.TaskTypeName))
                throw new BackGroundTaskException("任务类型名称不能为空");

            if (backGroundTaskBuilder.HasTaskArgs && backGroundTaskBuilder.TaskArgs == null)
                throw new BackGroundTaskException($"未设置任务 {backGroundTaskBuilder.TaskTypeName} 执行所需的参数!");

            // 序列化任务数据
            string? dataObject = null;
            if (backGroundTaskBuilder.HasTaskArgs && backGroundTaskBuilder.TaskArgs != null)
            {
                dataObject = JsonHelper.SerializeObject(backGroundTaskBuilder.TaskArgs);
            }

            // 创建作业和触发器
            var job = CreateJobDetail(
                backGroundTaskBuilder.TaskTypeName,
                dataObject,
                backGroundTaskBuilder.MaxRetryCountOnError,
                backGroundTaskBuilder.RetryIntervalSecond);

            var trigger = CreateTrigger(backGroundTaskBuilder.Priority, backGroundTaskBuilder.Delay);

            // 调度任务
            var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            await scheduler.ScheduleJob(job, trigger, cancellationToken);
            return job.Key.Name;
        }

        /// <summary>
        /// 添加无参数后台任务
        /// </summary>
        /// <typeparam name="TTask">任务类型</typeparam>
        /// <param name="priority">任务优先级</param>
        /// <param name="delay">延迟执行时间</param>
        /// <param name="maxRetryCountOnError">失败后最大重试次数</param>
        /// <param name="retryIntervalSecond">重试间隔（秒）</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>任务标识符</returns>
        public async Task<string> EnqueueAsync<TTask>(
            BackGroundTaskPriority priority = BackGroundTaskPriority.Medium,
            TimeSpan? delay = null,
            int? maxRetryCountOnError = null,
            int? retryIntervalSecond = null,
            CancellationToken cancellationToken = default) where TTask : IBackGroundTask
        {
            string taskTypeName = typeof(TTask).FullName ?? throw new BackGroundTaskException("无法获取任务类型的完全限定名");

            var job = CreateJobDetail(taskTypeName, null, maxRetryCountOnError, retryIntervalSecond);
            var trigger = CreateTrigger(priority, delay);

            var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            await scheduler.ScheduleJob(job, trigger, cancellationToken);

            return job.Key.Name;
        }

        /// <summary>
        /// 添加带参数后台任务
        /// </summary>
        /// <typeparam name="TTask">任务类型</typeparam>
        /// <typeparam name="TTaskData">任务数据类型</typeparam>
        /// <param name="data">任务数据</param>
        /// <param name="priority">任务优先级</param>
        /// <param name="delay">延迟执行时间</param>
        /// <param name="maxRetryCountOnError">失败后最大重试次数</param>
        /// <param name="retryIntervalSecond">重试间隔（秒）</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>任务标识符</returns>
        /// <exception cref="ArgumentNullException">任务数据为空时抛出</exception>
        public async Task<string> EnqueueAsync<TTask, TTaskData>(
            TTaskData data,
            BackGroundTaskPriority priority = BackGroundTaskPriority.Medium,
            TimeSpan? delay = null,
            int? maxRetryCountOnError = null,
            int? retryIntervalSecond = null,
            CancellationToken cancellationToken = default)
            where TTask : IBackGroundTask<TTaskData>
            where TTaskData : class
        {
            Check.NotNull(data, nameof(data));

            string taskTypeName = typeof(TTask).FullName ?? throw new BackGroundTaskException("无法获取任务类型的完全限定名");
            string dataJson = JsonHelper.SerializeObject(data);

            var job = CreateJobDetail(taskTypeName, dataJson, maxRetryCountOnError, retryIntervalSecond);
            var trigger = CreateTrigger(priority, delay);

            var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            await scheduler.ScheduleJob(job, trigger, cancellationToken);

            return job.Key.Name;
        }

        /// <summary>
        /// 根据优先级和延迟时间创建触发器
        /// </summary>
        /// <param name="priority">任务优先级</param>
        /// <param name="delay">延迟执行时间</param>
        /// <returns>Quartz 触发器实例</returns>
        private static ITrigger CreateTrigger(BackGroundTaskPriority priority, TimeSpan? delay)
        {
            int priorityValue = GetPriorityValue(priority);

            var triggerBuilder = TriggerBuilder.Create().WithPriority(priorityValue);

            if (delay.HasValue && delay.Value > TimeSpan.Zero)
            {
                triggerBuilder.StartAt(DateTimeOffset.UtcNow.Add(delay.Value));
            }
            else
            {
                triggerBuilder.StartNow();
            }

            return triggerBuilder.Build();
        }

        /// <summary>
        /// 将任务优先级枚举转换为 Quartz 优先级值
        /// </summary>
        /// <param name="priority">任务优先级枚举值</param>
        /// <returns>Quartz 优先级整数值</returns>
        private static int GetPriorityValue(BackGroundTaskPriority priority)
        {
            return priority switch
            {
                BackGroundTaskPriority.High => 10,
                BackGroundTaskPriority.Low => 1,
                BackGroundTaskPriority.Medium => 5,
                _ => 5, // 默认使用中等优先级
            };
        }

        /// <summary>
        /// 创建作业详情
        /// </summary>
        /// <param name="taskClassType">任务类型名称</param>
        /// <param name="dataObject">序列化的任务数据</param>
        /// <param name="maxRetryCountOnError">失败后最大重试次数</param>
        /// <param name="retryIntervalSecond">重试间隔（秒）</param>
        /// <returns>Quartz 作业详情实例</returns>
        private static IJobDetail CreateJobDetail(
            string? taskClassType,
            string? dataObject,
            int? maxRetryCountOnError = null,
            int? retryIntervalSecond = null)
        {
            // 生成唯一的作业名称，使用 GUID 和时间戳
            string uniqueJobName = $"job_{Guid.NewGuid():N}_{DateTimeHelper.TimestampSeconds()}";

            var jobBuilder = JobBuilder.Create<QuartzGenericJob>()
                .WithIdentity(uniqueJobName)
                .UsingJobData(KEY_TASK_CLASS_TYPE, taskClassType ?? string.Empty);

            // 添加重试配置
            if (maxRetryCountOnError.HasValue)
            {
                jobBuilder.UsingJobData(KEY_MAX_RETRY_COUNT, maxRetryCountOnError.Value.ToString());
            }

            if (retryIntervalSecond.HasValue)
            {
                jobBuilder.UsingJobData(KEY_RETRY_INTERVAL, retryIntervalSecond.Value.ToString());
            }

            // 添加任务数据
            if (!string.IsNullOrEmpty(dataObject))
            {
                jobBuilder.UsingJobData(KEY_DATA_OBJECT, dataObject);
            }

            return jobBuilder.Build();
        }
    }
}